A card grid is free-form — each row can contain whatever layout you like. A table is not: it has columns, a header row, and every row needs to line up under the same headings. That structural difference is exactly why the engine gives it a separate element, dynamiclisttable, rather than making you fake a table out of a styled dynamiclist.
Same Contacts table, same tenant scoping, this time with a department column added to make the tabular shape worth showing:
<Block tag='div'> <Element type='dynamiclisttable'> <Attributes class='contacts-table' /> <TableHeader> <Element type='html'> <Content><![CDATA[ <tr> <th>Name</th> <th>Department</th> <th>Email</th> <th class='num'>Open Orders</th> </tr> ]]></Content> </Element> </TableHeader> <TableRow> <Element type='html'> <Content><![CDATA[ <td class='name'>%contact_name%</td> <td><span class='dept-pill'>%department%</span></td> <td>%email%</td> <td class='num'>%open_order_count%</td> ]]></Content> </Element> </TableRow> <!-- one query, SQL last, same rule as dynamiclist --> <SQL> <query name='contacts_table'> <![CDATA[ SELECT c.contact_id, c.contact_name, c.department, c.email, COUNT(o.order_id) AS open_order_count FROM contacts c LEFT JOIN orders o ON o.contact_id = c.contact_id AND o.status = 'open' WHERE c.dept_id = '%deptid%' GROUP BY c.contact_id ORDER BY c.contact_name ASC ]]> </query> </SQL> </Element> </Block>
What's different from a dynamiclist
<Block>, with the dynamiclisttable nested one level in as its child, not as the root itself.<table class="contacts-table"> <thead> <tr> <th>Name</th><th>Department</th><th>Email</th><th class="num">Open Orders</th> </tr> </thead> <tbody> <tr> <td class="name">Sarah Whitfield</td> <td><span class="dept-pill">Operations</span></td> <td>s.whitfield@example.com</td> <td class="num">3</td> </tr> <!-- repeated once per row --> </tbody> </table>
| Name | Department | Open Orders | |
|---|---|---|---|
| Sarah Whitfield | Operations | s.whitfield@example.com | 3 |
| David Okonkwo | Procurement | d.okonkwo@example.com | 7 |
| Maria Santos | Finance | m.santos@example.com | 0 |
| James Cooper | Warehouse | j.cooper@example.com | 2 |
open_order_count column comes from a JOIN and a COUNT in the same single query — not a second round trip, not a per-row lookup. One query, every column.When to reach for which
Neither component is "better" — dynamiclisttable is purpose-built for column-based data: it generates the header row and keeps every column aligned underneath it automatically. dynamiclist is the more versatile of the two, capable of far more than card-style rows — including composing with dynamiclisttable in ways worth their own dedicated look. Post 3's grid and this post's table are rendering the same underlying contacts — dynamiclisttable just meant not having to hand-build the header row and column alignment ourselves.
A Detail Screen: single-record view with block-level SQL
Post 5 zooms into one contact. A different data-access rule entirely — a single row's data doesn't stay scoped to a row template the way list and table data do.